# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import logging

from coin.base.timestamp import get_timestamp
from coin.exchange.base.order_gateway import OrderSide

from coin.strategy.cheese.config import L2Config
from coin.strategy.cheese.sizers.l2 import L2Sizer
from coin.strategy.executor.passive.factory import construct_mm_executor
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.strategy.executor.passive.util import convert_executor_config_from_dict
from coin.strategy.hk_hard_arb.sizer.pass_agg import SingleProductSizeCalculator
from coin.proto.coin_executor_pb2 import PassiveExecutorConfig


class L2Executor(object):
  def __init__(self,
               l1_config,
               l2_config,
               logger,
               *,
               l2_pricer,
               l2_sizer,
               balance_printer,
               dry_run=False,
               strategy_logger=None):
    self._logger = logger or logging.getLogger(__name__)
    self._l1_config = l1_config
    self._l2_config = l2_config
    self._balance_printer = balance_printer

    self._products = self._l2_config.native_products
    self._dry_run = dry_run

    self._strategy_logger = strategy_logger

    # composite instances
    self._timestamp_last_agg = None

    # Passive executor
    self._execution_params_passive = l2_config.executor_config(self._products[0].symbol)
    assert self._execution_params_passive
    self._logger.info(self._execution_params_passive)

    self.mm_executor = None

    # state
    self._timestamp_last_book = 0
    self._timestamp_last_order = 0
    self._verbose = True
    self._l2_pricer = l2_pricer
    self._l2_sizer = l2_sizer
    self._warn_empty_ema = True
    l2_sizer.set_l2_executor(self)

  def on_book(self, product, exchange_id, book, available):
    self._timestamp_last_book = book.timestamp

  def on_og_reset(self, og):
    self.mm_executor = construct_mm_executor(self._l1_config.exchanges[0],
                                             self._execution_params_passive,
                                             og,
                                             self._logger)
    og.register_fill_callback(self.on_passive_fill)

  def on_passive_fill(self, fill_price, fill_qty, order):
    if order.product != self._products[0]:
      return False

    self._logger.info('[PASS:%s] FILL %s %f@%f' % (
        order.product,
        order.order_side,
        fill_qty,
        fill_price,
    ))
    return True

  def process_position(self):
    relpos = self._l2_sizer.get_relpos()
    self._l2_pricer.set_relpos(relpos)

  def _agg_available(self, timestamp):
    if not self._l2_config.agg_enabled():
      return False
    if self._timestamp_last_agg is None:
      return True
    if timestamp - self._timestamp_last_agg > self._l2_config.agg_period:
      return True
    return False

  def _manage_mm_orders(self, passive_prices):
    if passive_prices is None:
      return
    if self._execution_params_passive is None:
      return
    self.mm_executor.manage_mm_orders(sell_price=passive_prices.askp,
                                      buy_price=passive_prices.bidp,
                                      sell_qty=self._l2_config.lot_size,
                                      buy_qty=self._l2_config.lot_size,
                                      sell_price_pull=passive_prices.askp_pull,
                                      buy_price_pull=passive_prices.bidp_pull)

    # Dangerous area
    timestamp_now = get_timestamp()
    if self._agg_available(timestamp_now):
      fire_agg_buy = passive_prices.askt <= passive_prices.bidp_agg
      fire_agg_sell = passive_prices.bidt >= passive_prices.askp_agg
      assert not (fire_agg_buy and fire_agg_sell), passive_prices
      _, _, sell_qty, buy_qty = self.mm_executor._adjust_request(sell_price=None,
                                                                 buy_price=None,
                                                                 sell_qty=self._l2_config.lot_size,
                                                                 buy_qty=self._l2_config.lot_size)
      if fire_agg_buy and buy_qty is not None:
        self.mm_executor.force_fire_buy_order(buy_price=passive_prices.bidp_agg, buy_qty=buy_qty)
        self._logger.info('%s FIRE AGG BUY! (%s -> %s, %s)',
                          self._products[0].symbol,
                          passive_prices.bidp_agg,
                          passive_prices.askt,
                          buy_qty)
        self._timestamp_last_agg = timestamp_now
      if fire_agg_sell and sell_qty is not None:
        self.mm_executor.force_fire_sell_order(sell_price=passive_prices.askp_agg,
                                               sell_qty=sell_qty)
        self._logger.info('%s FIRE AGG SELL! (%s -> %s, %s)',
                          self._products[0].symbol,
                          passive_prices.askp_agg,
                          passive_prices.bidt,
                          sell_qty)
        self._timestamp_last_agg = timestamp_now

  def execute(self, timestamp):
    l2_prices = self._l2_pricer.get_prices()
    if l2_prices is None or l2_prices.ema is None:
      if self._warn_empty_ema:
        self._logger.warn("%s EMA is none", self._products[0])
        self._warn_empty_ema = False
      return
    if not self._warn_empty_ema:
      self._warn_empty_ema = True
      self._logger.warn("=== %s STARTING EXECUTION ===", self._products[0])
    passive_prices = self._l2_pricer.calculate_price_passive(l2_prices.target_price)
    self._manage_mm_orders(passive_prices)
